package org.qq120011676.fileserver.fileserver.service.impl;

import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.qq120011676.fileserver.fileserver.controller.resp.FileUploadResp;
import org.qq120011676.fileserver.fileserver.helper.StorageHelper;
import org.qq120011676.fileserver.fileserver.service.FileService;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import java.io.ByteArrayOutputStream;
import java.util.List;
import java.util.concurrent.*;

@RequiredArgsConstructor
@Service
public class FileServiceImpl implements FileService {
    private final StorageHelper storageHelper;
    private final ThreadPoolTaskExecutor threadPoolTaskExecutor;

    @SneakyThrows
    @Override
    public List<FileUploadResp> upload(List<MultipartFile> files) {
        List<Callable<FileUploadResp>> callableList = files.stream()
                .map(o -> (Callable<FileUploadResp>) () -> {
                    String sign = sign(o.getBytes());
                    FileUploadResp resp = new FileUploadResp();
                    resp.setSign(sign);
                    resp.setContentType(o.getContentType());
                    resp.setFilename(o.getOriginalFilename());
                    boolean bol = storageHelper.exist(sign);
                    if (bol) {
                        return resp;
                    }
                    storageHelper.save(sign, o.getBytes());
                    return resp;
                })
                .toList();
        List<Future<FileUploadResp>> futureList = threadPoolTaskExecutor.getThreadPoolExecutor()
                .invokeAll(callableList, 1L, TimeUnit.HOURS);
        return futureList.stream()
                .map(o -> {
                    try {
                        return o.get(10L, TimeUnit.SECONDS);
                    } catch (InterruptedException | ExecutionException | TimeoutException e) {
                        throw new RuntimeException(e);
                    }
                })
                .toList();
    }

    @SneakyThrows
    @Override
    public String uploadBytes(byte[] bytes) {
        String sign = sign(bytes);
        storageHelper.save(sign, bytes);
        return sign;
    }

    @SneakyThrows
    @Override
    public List<String> query(List<String> signs) {
        List<Callable<String>> callableList = signs.stream()
                .map(o -> (Callable<String>) () -> {
                    boolean bol = storageHelper.exist(o);
                    return bol ? o : null;
                })
                .toList();
        List<Future<String>> futureList = threadPoolTaskExecutor.getThreadPoolExecutor()
                .invokeAll(callableList, 10L, TimeUnit.SECONDS);
        return futureList.stream()
                .map(o -> {
                    try {
                        return o.get(10L, TimeUnit.SECONDS);
                    } catch (InterruptedException | ExecutionException | TimeoutException e) {
                        throw new RuntimeException(e);
                    }
                })
                .filter(StringUtils::hasText)
                .toList();
    }

    @SneakyThrows
    @Override
    public byte[] get(List<String> signs) {
        List<Callable<byte[]>> callableList = signs.stream()
                .map(o -> (Callable<byte[]>) () -> storageHelper.get(o))
                .toList();
        List<Future<byte[]>> futureList = threadPoolTaskExecutor.getThreadPoolExecutor()
                .invokeAll(callableList, 1L, TimeUnit.HOURS);
        List<byte[]> bytesList = futureList.stream()
                .map(o -> {
                    try {
                        return o.get(10L, TimeUnit.SECONDS);
                    } catch (InterruptedException | ExecutionException | TimeoutException e) {
                        throw new RuntimeException(e);
                    }
                })
                .toList();
        long fileSize = bytesList.stream()
                .mapToLong(bytes -> bytes.length)
                .sum();
        int size = fileSize > Integer.MAX_VALUE
                ? Integer.MAX_VALUE
                : (int) fileSize;
        try (ByteArrayOutputStream out = new ByteArrayOutputStream(size)) {
            for (byte[] bytes : bytesList) {
                out.write(bytes);
            }
            out.flush();
            return out.toByteArray();
        }
    }

    private String sign(byte[] bytes) {
        return DigestUtils.md5DigestAsHex(bytes);
    }
}